package dao

import (
	"context"
	"errors"
	"github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
	"strings"
	"time"
)

const (
	uniqueConflictsErrorNo uint16 = 1062
	supeaAministrator      string = "超级管理员"
)

var (
	ErrRoleDuplicate = errors.New("角色已存在")
)

type RbacDao interface {
	CreateRole(ctx context.Context, ard AuthRole) error
	UpdateRole(ctx context.Context, erd AuthRole) error
	DelRole(ctx context.Context, RoleID int32) error
	RoleLst(ctx context.Context) ([]AuthRole, error)

	Auth(ctx context.Context, RoleID int32, items []AuthItem) error
	GetRoleAuthData(ctx context.Context, RoleID int32) ([]AuthItem, error)
	GetAuthItemByRoleIds(ctx context.Context, RoleIds []int32) ([]int64, error)

	DoYouHavePermission(ctx context.Context, roleIds []int32, paths []string, isRead int32) bool
	IsSuperAdministrator(ctx context.Context, roleIds []int32) bool
}

type rbacDao struct {
	db *gorm.DB
}

func NewRbacDao(db *gorm.DB) RbacDao {
	return &rbacDao{
		db: db,
	}
}

func (s *rbacDao) CreateRole(ctx context.Context, ard AuthRole) error {
	now := time.Now().UnixMilli()
	ard.CreateTime = now
	ard.UpdateTime = now
	ard.Status = 1
	err := s.db.WithContext(ctx).Create(&ard).Error
	if mysqlErr, ok := err.(*mysql.MySQLError); ok {
		if mysqlErr.Number == uniqueConflictsErrorNo {
			return ErrRoleDuplicate
		}
	}
	return err
}

func (s *rbacDao) UpdateRole(ctx context.Context, erd AuthRole) error {
	now := time.Now().UnixMilli()
	erd.UpdateTime = now
	err := s.db.WithContext(ctx).Omit("create_time").Updates(&erd).Where("id = ?", erd.Id).Error
	return err
}

func (s *rbacDao) DelRole(ctx context.Context, RoleID int32) error {
	var roleData AuthRole
	return s.db.Model(&roleData).WithContext(ctx).Where("id = ? and name != ?", RoleID, supeaAministrator).Update("status", -1).Error
}

// RoleLst 角色列表数据
func (s *rbacDao) RoleLst(ctx context.Context) ([]AuthRole, error) {
	var role []AuthRole
	err := s.db.WithContext(ctx).Where("status = ?", 1).Find(&role).Error

	return role, err
}

func (s *rbacDao) DoYouHavePermission(ctx context.Context, roleIds []int32, paths []string, isRead int32) bool {
	var count int64
	s.db.WithContext(ctx).Model(&AuthItem{}).Where("role_id IN (?) and menu_url IN (?) and is_read = ?", roleIds, paths, isRead).Count(&count)
	return count > 0
}

func (s *rbacDao) IsSuperAdministrator(ctx context.Context, roleIds []int32) bool {
	var count int64
	s.db.WithContext(ctx).Model(&AuthRole{}).Where("id IN (?) and name = ?", roleIds, supeaAministrator).Count(&count)
	return count > 0
}

// Auth 权限
func (s *rbacDao) Auth(ctx context.Context, RoleID int32, items []AuthItem) error {
	var authItem []AuthItem
	err := s.db.WithContext(ctx).Where("role_id = ? ", RoleID).Delete(&authItem).Error
	if err != nil {
		return err
	}
	tx := s.db.WithContext(ctx).Begin() // 开启事务
	for i := range items {
		if items[i].MenuUrl != "" && items[i].MenuUrl != "/" {
			values := strings.Split(items[i].MenuUrl, ",")
			for _, value := range values {
				items[i].RoleID = int64(RoleID)
				items[i].MenuUrl = value
				if err := tx.Create(&items[i]).Error; err != nil {
					tx.Rollback()
					return err
				}
			}
		}
	}
	return tx.Commit().Error
}

// GetRoleAuthData 根据角色获取授权的菜单
func (s *rbacDao) GetRoleAuthData(ctx context.Context, RoleID int32) ([]AuthItem, error) {
	var authItem []AuthItem
	err := s.db.WithContext(ctx).Where("role_id = ?", RoleID).Find(&authItem).Error
	return authItem, err
}

// GetAuthItemByRoleIds 获取该角色下授权的菜单
func (s *rbacDao) GetAuthItemByRoleIds(ctx context.Context, RoleIds []int32) ([]int64, error) {
	var menuIDs []int64
	err := s.db.WithContext(ctx).Model(&AuthItem{}).Where("role_id IN (?)", RoleIds).Pluck("DISTINCT menu_id", &menuIDs).Error
	return menuIDs, err
}

type AuthRole struct {
	Id          int32
	Status      int32
	Name        string
	Description string
	CreateTime  int64
	UpdateTime  int64
}

type AuthItem struct {
	RoleID  int64
	MenuUrl string
	IsRead  int32
	MenuId  int64
}
